import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;
import java.lang.Runtime;
import java.lang.Thread;

public class Client extends UnicastRemoteObject implements Client_itf {

	private static Client client;
	public static Hashtable<Integer, SharedObject> ids = new Hashtable<Integer, SharedObject>();
	private static Server_itf server;
	private static int IHM = 1;
	private static ClientIHM_itf ihm;

	public Client() throws RemoteException {
		super();
	}


///////////////////////////////////////////////////
//         Interface to be used by applications
///////////////////////////////////////////////////

	// initialization of the client layer
	/**
	 * Initialize the class and connect to the server
	 * Need to be called before using the class Client
	 */ 
	public static void init() {
		try {
			client = new Client();
			server = (Server_itf)Naming.lookup("Coordinator");
			if(IHM == 1) {
				ihm = new ClientIHMView(ids);
			}
		} catch (Exception exc) {
			exc.printStackTrace();
		}
	}
	
	// lookup in the name server
	/**
	 * Search in the server and return the SharedObject maped by this name
	 */
	public static SharedObject lookup(String name) {
		try {
			Descriptor desc = server.lookup(name);
			if(desc == null) {
				System.out.println("The object named " + name + " doesn't exist in the server.");
				return null;
			} else {
				System.out.println("The object named " + name + " has been found in the server.");
				int id = desc.id;
				
				new GenStub(desc.type.getName());
				Runtime rt = Runtime.getRuntime();
				rt.exec("javac " + desc.type.getName() + "_stub.java");
				Thread.sleep(1000);
				
				SharedObject so = (SharedObject) Class.forName(desc.type.getName()+"_stub").newInstance();
				so.obj = desc.obj;
				so.setId(id);
				
				ids.put(id, so);
				if(IHM == 1) {
					ihm.register(id, name);
				}
				return so;
			}
		} catch (Exception exc) {
			exc.printStackTrace();
			System.out.println("Object not received from the server");
			return null;	//that's why the client may make Irc try to "reregister" an existing entry. (NameAlreadyExistsException on adding a second client) 
		} finally {
			if(IHM == 1) {
				ihm.maj();
			}
		}
	}		
	
	// binding in the name server
	/**
	 * Saved a SharedObject in the server
	 */
	public static void register(String name, SharedObject_itf so) {
		int id = 0;
		try {
			id = ((SharedObject)so).getId();
			server.register(name, id);
			if(IHM == 1) {
				ihm.register(id, name);
			}
			System.out.println("The object : " + name + " has been added to the server.");

		} catch (Exception exc) {
			System.err.println("Error while registering, the program will close.");
			exc.printStackTrace();
			System.exit(0);
		} finally {
			if(IHM == 1) {
				ihm.maj();
			}
		}
	}

	// creation of a shared object
	/**
	 * @param an Object
	 * @return a SharedObject created with the param Object
	 */
	public static SharedObject create(Object o) {
		try {
			int id = server.create(o);
			
			new GenStub(o.getClass().getName());
			Runtime rt = Runtime.getRuntime();
			rt.exec("javac " + o.getClass().getName() + "_stub.java");
			Thread.sleep(1000);
			
			SharedObject so = (SharedObject) Class.forName(o.getClass().getName()+"_stub").newInstance();
			so.setId(id);
			ids.put(id, so);
			System.out.println("The object with the ID : " + id + " has been created.");
			return so;
		} catch(Exception exc) {
			exc.printStackTrace();
			System.exit(-1);
			return null;
		} finally {
			if(IHM == 1) {
				ihm.maj();
			}
		}
	}
	
/////////////////////////////////////////////////////////////
//    Interface to be used by the consistency protocol
////////////////////////////////////////////////////////////

	// request a read lock from the server
	/**
	 * @return 
	 */
	public static Object lock_read(int id) {
		try {
			//System.out.print("Asking lock_read...");
			Object o = server.lock_read(id, client);
			//System.out.println("lock_read given !");
			if(IHM == 1) {
				ihm.maj();
			}
			return o;
		} catch (Exception exc) {
			exc.printStackTrace();
			return null;
		}
	}

	// request a write lock from the server
	public static Object lock_write (int id) {
		try {
			//System.out.print("Asking lock_write...");
			Object o = server.lock_write(id, client);
			//System.out.println("lock_write given !");
			if(IHM == 1) {
				ihm.maj();
			}
			return o;
		} catch (Exception exc) {
			exc.printStackTrace();
			return null;
		}
	}

	// receive a lock reduction request from the server
	public Object reduce_lock(int id) throws java.rmi.RemoteException {
		SharedObject shao = ids.get(id);
		Object o = null;
		synchronized(shao) {
			if(shao == null)
				System.out.println("Client: Trying to do null.reduce_lock()! :-(");
			o = shao.reduce_lock();
		}
		if(IHM == 1) {
			ihm.maj();
		}
		return o;
	}


	// receive a reader invalidation request from the server
	public void invalidate_reader(int id) throws java.rmi.RemoteException {
		SharedObject shao = ids.get(id);
		synchronized(shao) {
				shao.invalidate_reader();
		}
		if(IHM == 1) {
			ihm.maj();
		}
	}


	// receive a writer invalidation request from the server
	public Object invalidate_writer(int id) throws java.rmi.RemoteException {
		SharedObject shao   = ids.get(id);
		Object o = null;
		synchronized(shao){
			o = shao.invalidate_writer();
		}
		if(IHM == 1) {
			ihm.maj();
		}
		return o;
		 
	}

	public static void maj() {
		if(IHM == 1) {
			ihm.maj();
		}
	}

	public static void close() {
		System.exit(0);
	}
}
